package org.jing.core.lang.data.json;

import org.jing.core.lang.JingException;

/**
 * Description: json解析序列化 <br>
 *
 * @author bks <br>
 * @since 2024-07-22 <br>
 */
@SuppressWarnings("Duplicates")
public class Tokenizer {
    private final CharReader charReader;

    private Token previousToken;

    private Token currentToken;

    /**
     * Description: Json序列化. <br>
     *
     * @param charReader <br>
     */
    public Tokenizer(CharReader charReader) {
        this.charReader = charReader;
    }

    public Token next() {
        previousToken = currentToken;
        char ch;
        do {
            if (!charReader.hasMore()) {
                return new Token(TokenType.END_DOCUMENT, null);
            }
            ch = charReader.next();
        }
        while (isWhiteSpace(ch));
        switch (ch) {
            case '{':
                return (currentToken = new Token(TokenType.BEGIN_OBJECT, String.valueOf(ch)));
            case '}':
                return (currentToken = new Token(TokenType.END_OBJECT, String.valueOf(ch)));
            case '[':
                return (currentToken = new Token(TokenType.BEGIN_ARRAY, String.valueOf(ch)));
            case ']':
                return (currentToken = new Token(TokenType.END_ARRAY, String.valueOf(ch)));
            case ',':
                return (currentToken = new Token(TokenType.SEP_COMMA, String.valueOf(ch)));
            case ':':
                return (currentToken = new Token(TokenType.SEP_COLON, String.valueOf(ch)));
            case 'n':
                return (currentToken = readNull());
            case 't':
            case 'f':
                return (currentToken = readBoolean());
            case '"':
                return (currentToken = readString());
            case '-':
                return (currentToken = readNumber());
        }

        if (isDigit(ch)) {
            return (currentToken = readNumber());
        }

        throw new JingException("illegal character");
    }

    public Token previous() {
        if (null == previousToken) {
            throw new JingException("no previous token");
        }
        return previousToken;
    }

    private boolean isWhiteSpace(char ch) {
        return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
    }

    private Token readString() {
        StringBuilder sb = new StringBuilder();
        for (;;) {
            char ch = charReader.next();
            if (ch == '\\') {
                ch = charReader.next();
                switch (ch) {
                    case '"':
                    case '\\':
                    case '/':
                        sb.append(ch);
                        break;
                    case 'u':
                        StringBuilder tmp = new StringBuilder();
                        for (int i = 0; i < 4; i++) {
                            ch = charReader.next();
                            if (isHex(ch)) {
                                tmp.append(ch);
                            }
                            else {
                                throw new JingException("invalid character");
                            }
                        }
                        sb.append((char) Integer.parseInt(tmp.toString(), 16));
                        break;
                    case 'r':
                        sb.append('\r');
                        break;
                    case 'n':
                        sb.append('\n');
                        break;
                    case 'b':
                        sb.append('\b');
                        break;
                    case 't':
                        sb.append('\t');
                        break;
                    case 'f':
                        sb.append('\f');
                        break;
                    case '\'':
                        sb.append("\\'");
                        break;
                    default:
                        throw new JingException("invalid escape character");
                }
            }
            else if (ch == '"') {
                return new Token(TokenType.STRING, sb.toString());
            }
            else if (ch == '\r' || ch == '\n') {
                throw new JingException("invalid character");
            }
            else {
                sb.append(ch);
            }
        }
    }

    private boolean isHex(char ch) {
        return ((ch >= '0' && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F'));
    }

    private Token readNumber() {
        char ch = charReader.peek();
        StringBuilder sb = new StringBuilder();
        if (ch == '-') {    // 处理负数
            sb.append(ch);
            ch = charReader.next();
            if (ch == '0') {    // 处理 -0
                sb.append(ch);
                sb.append(readFracAndExp());
            }
            else if (isDigitOne2Nine(ch)) {
                do {
                    sb.append(ch);
                    ch = charReader.next();
                }
                while (isDigit(ch));
                if (ch != (char) -1) {
                    charReader.back();
                    sb.append(readFracAndExp());
                }
            }
            else {
                throw new JingException("invalid minus number");
            }
        }
        else if (ch == '0') {    // 处理小数
            sb.append(ch);
            sb.append(readFracAndExp());
        }
        else {
            do {
                sb.append(ch);
                ch = charReader.next();
            }
            while (isDigit(ch));
            if (ch != (char) -1) {
                charReader.back();
                sb.append(readFracAndExp());
            }
        }
        return new Token(TokenType.NUMBER, sb.toString());
    }

    private boolean isExp(char ch) {
        return ch == 'e' || ch == 'E';
    }

    private boolean isDigit(char ch) {
        return ch >= '0' && ch <= '9';
    }

    private boolean isDigitOne2Nine(char ch) {
        return ch >= '1' && ch <= '9';
    }

    private String readFracAndExp() {
        StringBuilder sb = new StringBuilder();
        char ch = charReader.next();
        if (ch == '.') {
            sb.append(ch);
            ch = charReader.next();
            if (!isDigit(ch)) {
                throw new JingException("invalid frac");
            }
            do {
                sb.append(ch);
                ch = charReader.next();
            }
            while (isDigit(ch));

            if (isExp(ch)) {    // 处理科学计数法
                sb.append(ch);
                sb.append(readExp());
            }
            else {
                if (ch != (char) -1) {
                    charReader.back();
                }
            }
        }
        else if (isExp(ch)) {
            sb.append(ch);
            sb.append(readExp());
        }
        else {
            charReader.back();
        }

        return sb.toString();
    }

    private String readExp() {
        StringBuilder sb = new StringBuilder();
        char ch = charReader.next();
        // 第1步：处理可选的符号（'+' 或 '-'）
        if (ch == '+' || ch == '-') {
            sb.append(ch);
            ch = charReader.next(); // 消耗掉符号，读取下一个字符
        }
        // 第2步：处理必须存在的数字
        if (isDigit(ch)) {
            do {
                sb.append(ch);
                ch = charReader.next();
            } while (isDigit(ch));
            if (ch != (char) -1) {
                charReader.back();
            }
        } else {
            // 如果符号后面没有数字，或者e/E后面直接就是非数字，则抛出异常
            throw new JingException("e or E");
        }
        return sb.toString();
    }

    private Token readBoolean() {
        if (charReader.peek() == 't') {
            if (!(charReader.next() == 'r' && charReader.next() == 'u' && charReader.next() == 'e')) {
                throw new JingException("invalid json string");
            }

            return new Token(TokenType.BOOLEAN, "true");
        }
        else {
            if (!(charReader.next() == 'a' && charReader.next() == 'l' && charReader.next() == 's'
                && charReader.next() == 'e')) {
                throw new JingException("invalid json string");
            }

            return new Token(TokenType.BOOLEAN, "false");
        }
    }

    private Token readNull() {
        if (!(charReader.next() == 'u' && charReader.next() == 'l' && charReader.next() == 'l')) {
            throw new JingException("invalid json string");
        }

        return new Token(TokenType.NULL, "null");
    }
}
